本篇將延伸上一篇討論的L2 Regularization,延伸到所謂的Lp Regualrization,及其在Deep Learning中比較粗暴的作法。是作者本人覺得很喜歡的一個技術,不過實務上提昇通常很有限就是(汗)。
顧名思義,Lp Regularization,是利用Lp去進行Regularization的方式。而除了前一篇提過的L2是歐基理德距離以外,所謂的Lp指得便是線性代數中的Lp-norm:
具體限制的方式一樣是把沒有開根號版本的Lp-norm加入loss,(通常是為了比較好微分、好算梯度、以及避免rounding error)再進行學習,可以寫成類似下面的概念式:
如此一來,除了可以達到對權重的限制以外,也可以根據p這個超參數的設置,進一步進行不同的限制效果。
具體實例可以參考上一篇也有提到Google爸爸的這個視覺化工具。裡面有L1跟L2的比較,大約總結底下幾項:
上一篇文章有提過,只要使用Optimizer中的weight decay就可以達到類似L2 Regularization的限制效果。但其實還有一個差異尚未提到的則是,由於weight decay是直接實作在整個梯度,因此整個Regularization的範圍是模型內所有的參數。傳統的迴歸僅作用在乘法項的權重,而不作用在截距項的BIAS,因此實際上還是不大一樣。
由於筆者是統計出身的,總覺得連帶截距項也懲罰很奇怪。因此本作為本篇文章的目標,決定自刻一個只作用在乘法項的權重的Lp Regularization實作。
實作方法一樣可以透過增加lightning-module內的函數來作到,首先新增對應的超參數到hparma.yaml
train:
weight_decay:
p: 2
lambda: 0.00001 # 0.01 * 0.001
然後新增計算lp power sum的函數:
def lp_power_sum(tensor, p):
if p == 2:
return (tensor ** 2).sum()
else:
return (tensor.abs() ** p).sum()
將利用lp power sum計算懲罰項的函數到增加lightning-module內:
def lp_penalty(self):
reg_loss = 0
for name, weight in self.named_parameters():
if 'weight' in name:
reg_loss += lp_power_sum(weight, p = self.CONFIG['train']['weight_decay']['p'])
return reg_loss
最後更改train_step
內計算loss的方式:
def training_step(self, batch: Any, batch_idx: int):
inputs, preds, labels, loss = self.step(batch)
self.log('train/loss', loss.item(), on_step=True, on_epoch=True, batch_size = inputs.shape[0])
reg_loss = self.lp_norm()
self.log('train/reg_loss', reg_loss.item(), on_step=True, on_epoch=True, batch_size = inputs.shape[0])
reg_loss = self.lp_penalty()
self.log('train/reg_psum_loss', reg_loss.item(), on_step=True, on_epoch=True, batch_size = inputs.shape[0])
if self.CONFIG['train']['weight_decay']['lambda'] > 0:
loss += reg_loss * self.CONFIG['train']['weight_decay']['lambda']
self.log('train/total_loss', loss.item(), on_step=True, on_epoch=True, batch_size = inputs.shape[0])
return loss
基本上就大功告成了!
另外解釋幾點:
lp_norm
,純粹是希望log能與作比較。一樣請參考這個commit進行實驗。
跟前幾次相比的結果如下圖: